package com.huanling.main.dataflow;

import cn.hutool.core.map.MapUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.huanling.main.enums.ReadTypeEnum;
import com.huanling.main.keyconflictdealer.*;

import java.io.File;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;

/**
 * 处理流程类  对应---Control 数据端
 *
 * @author guibo.sun
 * @since 16 七月 2021
 */
public class DataDeal {
    private File originPath;

    private File comparePath;

    private String outputDictionary;

    private ReadTypeEnum readTypeEnum;

    private String firstColumn;

    /**
     * 是否需要对比数据差异，如不对比，就只输出A有B没有，和A没有B有的两种数据了
     */
    private boolean doDiff = true;

    private Map<String, Map<String, Object>> hashMap;

    private AbstractConflictKeyDealer conflictKeyDealer;

    private Function<Map<String, Object>, String> compareKeyGenerator;

    private BiPredicate<Map<String, Object>, Map<String, Object>> valueEqualsJudge;

    private final ConflictKeyContext conflictKeyContext = new ConflictKeyContext();

    private final ResultContext resultContext = new ResultContext();


    public void deal() {
        init();
        buildHashMap();
        compareAndBuildResult();
        outPut();
    }

    private void init() {
        conflictKeyContext.setCompareKeyGenerator(compareKeyGenerator).setValueEqualsJudge(valueEqualsJudge);
    }

    private void outPut() {
        String path = outputDictionary + UUID.randomUUID() + "AnotherOnly.xlsx";
        ExcelWriter anotherOnlyWriter = ExcelUtil.getBigWriter(path);
        anotherOnlyWriter.write(resultContext.getAnotherOnlyList());
        anotherOnlyWriter.flush();
        ExcelWriter originOnlyWriter = ExcelUtil.getBigWriter(outputDictionary + UUID.randomUUID() + "OriginOnly.xlsx");
        originOnlyWriter.write(resultContext.getOriginOnlyList());
        originOnlyWriter.flush();
        if (doDiff) {
            ExcelWriter differentWriter = ExcelUtil.getBigWriter(outputDictionary + UUID.randomUUID() + "DifferentDataList.xlsx");
            differentWriter.write(resultContext.getDifferList());
            differentWriter.flush();
        }

        if (LogNeeded.class.isAssignableFrom(conflictKeyDealer.getClass())) {
            LogNeeded logNeededDealer = (LogNeeded) this.conflictKeyDealer;
            ExcelWriter logWriter = ExcelUtil.getBigWriter(outputDictionary + UUID.randomUUID() + "AbnormalDataList.xlsx");
            logWriter.write(logNeededDealer.getLogDataList());
            logWriter.flush();
        }
    }

    private void compareAndBuildResult() {
        ExcelReader compareReader = ExcelUtil.getReader(comparePath);
        List<Map<String, Object>> anotherData = compareReader.readAll();
        for (Map<String, Object> anotherDatum : anotherData) {
            String key = getCompareKeyGenerator().apply(anotherDatum);
            Map<String, Object> originDatum = hashMap.get(key);
            if (Objects.nonNull(originDatum)) {
                if (!doDiff) {
                    continue;
                }
                boolean equals = getValueEqualsJudge().test(anotherDatum, originDatum);
                if (!equals) {
                    // 如果是 ZipperFindDealer 的话
                    if (ZipperFindDealer.class.isAssignableFrom(conflictKeyDealer.getClass())) {
                        boolean present = ((ZipperFindDealer) conflictKeyDealer).bestEffortSearch(key, anotherDatum).isPresent();
                        if (present) {
                            continue;
                        }
                    }
                    originDatum.forEach((column, columnData) -> {
                        if (!Objects.equals(anotherDatum.get(column), columnData)) {
                            originDatum.put(column, "\"" + columnData + "\"\\" + "\"" + anotherDatum.get(column) + "\"");
                        }
                    });
                    resultContext.getDifferList().add(originDatum);
                }
            } else {
                resultContext.getAnotherOnlyList().add(anotherDatum);
            }
            hashMap.remove(key);
        }
        resultContext.getOriginOnlyList().addAll(hashMap.values());
    }

    private void buildHashMap() {
        ExcelReader originReader = ExcelUtil.getReader(originPath);
        List<Map<String, Object>> originData = originReader.readAll();
        hashMap = MapUtil.newHashMap(originData.size());
        originData.forEach(row -> {
            String key = getCompareKeyGenerator().apply(row);
            hashMap.compute(key, getConflictKeyBiFunc(row));
        });
    }

    private BiFunction<String, Map<String, Object>, Map<String, Object>> getConflictKeyBiFunc(Map<String, Object> newValue) {
        return Optional.ofNullable(conflictKeyDealer).map(item -> item.getDealer(newValue, this))
                .orElseGet(() -> {
                    conflictKeyDealer = new DefaultConflictKeyDealer();
                    return conflictKeyDealer.getDealer(newValue, this);
                });
    }


    public BiPredicate<Map<String, Object>, Map<String, Object>> getValueEqualsJudge() {
        return Optional.ofNullable(valueEqualsJudge).orElse((Map<String, Object> oldValue, Map<String, Object> newValue) -> {
            for (Map.Entry<String, Object> entry : oldValue.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (!Objects.equals(value, newValue.get(key))) {
                    return false;
                }
            }
            return true;
        });
    }

    public Function<Map<String, Object>, String> getCompareKeyGenerator() {
        return Optional.ofNullable(compareKeyGenerator).orElse(map -> map.get(firstColumn).toString());
    }

    public ConflictKeyContext getConflictKeyContext() {
        return conflictKeyContext;
    }


    public DataDeal withCompareKeyGenerator(Function<Map<String, Object>, String> compareKeyGenerator) {
        this.compareKeyGenerator = compareKeyGenerator;
        return this;
    }

    public DataDeal withValueEqualsJudge(BiPredicate<Map<String, Object>, Map<String, Object>> valueEqualsJudge) {
        this.valueEqualsJudge = valueEqualsJudge;
        return this;
    }

    public DataDeal withOriginPath(File originPath) {
        this.originPath = originPath;
        return this;
    }

    public DataDeal withComparePath(File comparePath) {
        this.comparePath = comparePath;
        return this;
    }

    public DataDeal withReadTypeEnum(ReadTypeEnum readTypeEnum) {
        this.readTypeEnum = readTypeEnum;
        return this;
    }

    public DataDeal withOutputDictionary(String outputDictionary) {
        this.outputDictionary = outputDictionary;
        return this;
    }

    public DataDeal withFirstColumn(String firstColumn) {
        this.firstColumn = firstColumn;
        return this;
    }

    public DataDeal withDoDiff(boolean doDiff) {
        this.doDiff = doDiff;
        return this;
    }

    public DataDeal withConflictKeyDealer(AbstractConflictKeyDealer conflictKeyDealer) {
        this.conflictKeyDealer = conflictKeyDealer;
        return this;
    }
}
